library(tidyverse)
steam <- read_csv("raw_data/steam_checkpoint_2.csv")
Rows: 26564 Columns: 27── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (10): name, developer, publisher, controller_support, multiplayer, categories, genres, steamspy_tags, general_rating, owners
dbl   (9): appid, required_age, achievements, positive_ratings, negative_ratings, average_playtime, median_playtime, price, total_reviews
lgl   (7): free_to_play, virtual_reality_support, steam_workshop, singleplayer, windows_support, mac_support, linux_support
date  (1): release_date
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
steam
steam %>% 
  select(name, owners)

#For the purposes of making a model, what would we want the target to be? If we were to create a column called “successful”, what variables would it look for?


steam %>%
  separate(col = steamspy_tags, sep = ";", into = c("tag_1", "tag_2", "tag_3")) %>% 
  pivot_longer(cols = c("tag_1", "tag_2", "tag_3"), values_to = "tags", names_to = "drop_me") %>% 
  drop_na(tags) %>% 
  group_by(tags) %>% 
   count(tags) %>% 
   arrange(desc(n)) 
Warning: Expected 3 pieces. Missing pieces filled with `NA` in 2551 rows [1283, 3566, 3669, 3711, 4035, 4146, 4457, 4733, 4822, 5019, 5140, 5142, 5264, 5361, 5378, 5497, 5553, 5556, 5570, 5756, ...].
# creating new logical columns for use in the model - we cant have every genre/tag, so a selection of a few will have to do
# Preferably ones without a lot of overlap
# Not including Indie just now, because anything developed by Dinkey game would be Indie by default - It's a tag that doesnt really describe anything about the game
steam <- steam %>% 
  mutate(is_open_world = ifelse(str_detect(steamspy_tags, "Open World"), TRUE, FALSE),
         is_rogue_like = ifelse(str_detect(steamspy_tags, "Rogue-like"), TRUE, FALSE),
         is_metroidvania = ifelse(str_detect(steamspy_tags, "Metroidvania"), TRUE, FALSE),
         is_visual_novel = ifelse(str_detect(steamspy_tags, "Visual Novel"), TRUE, FALSE),
         has_great_soundtrack = ifelse(str_detect(steamspy_tags, "Great Soundtrack"), TRUE, FALSE),
         is_shooter =ifelse(str_detect(steamspy_tags, "Shooter") | str_detect(steamspy_tags, "FPS") | str_detect(steamspy_tags, "Third-Person Shooter"), TRUE, FALSE),
         is_rpg = ifelse(str_detect(steamspy_tags, "RPG"), TRUE, FALSE),
         has_female_protagonist = ifelse(str_detect(steamspy_tags, "Female Protagonist"), TRUE, FALSE),
         is_sports = ifelse(str_detect(steamspy_tags, "Sports"), TRUE, FALSE),
         is_simulation = ifelse(str_detect(steamspy_tags, "Simulation"), TRUE, FALSE),
                                .after = multiplayer) 
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
steam <- steam %>% 
  mutate(is_positive = ifelse(str_detect(general_rating, "Positive"), TRUE, FALSE),
         .after = linux_support)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name

For the purpose of my model over in Python land, i’m turning Multiplayer into just a logical to see if it changes anything

steam %>% 
  distinct(multiplayer)
steam <- steam %>% 
  mutate(multiplayer = ifelse(str_detect(multiplayer, "No multiplayer"), FALSE, TRUE)) 
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
#ok fine im adding indie to see what happens 
steam <- steam %>% 
  mutate(is_indie = ifelse(str_detect(steamspy_tags, "Indie"), TRUE, FALSE)) 
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
steam %>% 
  filter(multiplayer == FALSE)
# what if we bumped up the threshold? What if mostly positive was no longer acceptable?

steam_but_harsher <- steam %>% 
  mutate(is_positive = case_when(
    general_rating == "Extremely Positive" | general_rating == "Positive" ~ TRUE,
    TRUE ~ FALSE
  )) 
write_csv(steam, "clean_data/steam_for_model.csv")
write_csv(steam_but_harsher, "clean_data/harsher_steam_for_model.csv")

For now, im gonna do some text stuff using backloggd reviews

backloggd_reviews <- read_csv("clean_data/backloggd_reviews.csv")
Rows: 1509 Columns: 8── Column specification ─────────────────────────────────────────────────────────────
Delimiter: ","
chr (7): title, summary, reviews, genre_tag, genre_tag_2, genre_tag_3, genre_tag_4
dbl (1): rating
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# separating reviews into their own columns so i can manage them
reviews_separated <- backloggd_reviews %>% 
  separate_longer_delim(cols = "reviews", delim = "',\ \'") %>% 
  separate_longer_delim(cols = "reviews", delim = "\',\ \"") %>% 
  separate_longer_delim(cols = "reviews", delim = "\",\ \'") %>%  
  separate_longer_delim(cols = "reviews", delim = "\",\ \"") %>% 
  unique() %>% 
  # removing all the opening and closing rubbish
  mutate(reviews = str_remove_all(string = reviews, pattern = "\\[\""),
         reviews = str_remove_all(string = reviews, pattern = "\\[\'"),
         reviews = str_remove_all(string = reviews, pattern = "\"\\]"),
         reviews = str_remove_all(string = reviews, pattern = "\'\\]")) 
reviews_separated %>% 
  group_by(title) %>% 
  count(title)

lets split this into ratings, like i did with the other backloggd thing

reviews_separated <- reviews_separated %>% 
  mutate(rating_range = case_when(
    rating < 5 & rating >= 4 ~ "4+",
    rating < 4 & rating >= 3 ~ "3 to 4",
    rating < 3 & rating >= 2 ~ "2 to 3",
    rating < 2 & rating >= 1 ~ "1 to 2",
    rating < 1 ~ ">1",
    TRUE ~ "No rating"
  ))

Before doing text analysis, lets convert everything to lower case - this might take away some context via capitalisation, but we only have 6 reviews per game so gotta work with what we have

reviews_lower_separated <- reviews_separated %>% 
  mutate(reviews = tolower(reviews)) #%>% 
  #filter(reviews %in% c("í", "ñ", "é", "á", "ó", "á") == FALSE)
library(textdata)
library(tidytext)
de_stopwords <- tibble(word = stopwords("de"))
Error in stopwords("de") : could not find function "stopwords"

reviews_3_to_lowest_stop <- reviews_lower_separated %>% 
  filter(rating_range == "2 to 3" | rating_range == "1 to 2" | rating_range == ">1") %>% 
  select(-summary) %>% 
  unnest_tokens(input = reviews, output = word) %>% 
  anti_join(filter(stop_words, lexicon == "SMART")) %>% 
  anti_join(game_stop_words) %>% 
  anti_join(pt_stopwords) %>% 
  anti_join(de_stopwords) %>% 
  count(word, sort = TRUE)
Joining with `by = join_by(word)`Joining with `by = join_by(word)`Joining with `by = join_by(word)`Joining with `by = join_by(word)`

bigrams might be more appropriate for bad reviews

bigrams_3_to_lowest_stop <- reviews_lower_separated %>% 
  filter(rating_range == "2 to 3" | rating_range == "1 to 2" | rating_range == ">1") %>% 
  unnest_tokens(bigram, reviews, token = "ngrams", n = 2) %>% 
  count(bigram, sort = TRUE) %>%  # count bigrams
  separate(bigram, into = c("word1", "word2"), sep = " ") %>% # split bigrams into two seperate columns
  anti_join(stop_words, join_by("word1" == "word")) %>%  # check if word1 of bigram is a stop word
  anti_join(stop_words, join_by("word2" == "word")) %>% 
  anti_join(pt_stopwords, join_by("word1" == "word")) %>% 
  anti_join(pt_stopwords, join_by("word2" == "word")) %>% 
  anti_join(game_stop_words, join_by("word1" == "word")) %>% 
  anti_join(game_stop_words, join_by("word2" == "word")) %>% 
  anti_join(de_stopwords, join_by("word1" == "word")) %>% 
  anti_join(de_stopwords, join_by("word2" == "word")) %>% 
  drop_na() %>% 
  unite(col = "bigram", word1:word2, sep = " ", remove = TRUE) %>% 
  anti_join(game_stop_bigrams)
Joining with `by = join_by(bigram)`

well fine lets remove game names as well

ok that still kinda sucks

  ggwordcloud(words = bigrams_3_to_5_no_stop$bigram, freq = bigrams_3_to_5_no_stop$n, random.color = TRUE, colors =  c("#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"))

ok what if we try some of that tf-idf stuff

``

LS0tDQp0aXRsZTogIkRheSBUaGUgRm91ciINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KYGBge3J9DQpzdGVhbSA8LSByZWFkX2NzdigicmF3X2RhdGEvc3RlYW1fY2hlY2twb2ludF8yLmNzdiIpDQpgYGANCg0KYGBge3J9DQpzdGVhbQ0KYGBgDQoNCmBgYHtyfQ0Kc3RlYW0gJT4lIA0KICBzZWxlY3QobmFtZSwgb3duZXJzKQ0KYGBgDQoNCiNGb3IgdGhlIHB1cnBvc2VzIG9mIG1ha2luZyBhIG1vZGVsLCB3aGF0IHdvdWxkIHdlIHdhbnQgdGhlIHRhcmdldCB0byBiZT8gSWYgd2Ugd2VyZSB0byBjcmVhdGUgYSBjb2x1bW4gY2FsbGVkICJzdWNjZXNzZnVsIiwgd2hhdCB2YXJpYWJsZXMgd291bGQgaXQgbG9vayBmb3I/DQoNCmBgYHtyfQ0KDQpzdGVhbSAlPiUNCiAgc2VwYXJhdGUoY29sID0gc3RlYW1zcHlfdGFncywgc2VwID0gIjsiLCBpbnRvID0gYygidGFnXzEiLCAidGFnXzIiLCAidGFnXzMiKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IGMoInRhZ18xIiwgInRhZ18yIiwgInRhZ18zIiksIHZhbHVlc190byA9ICJ0YWdzIiwgbmFtZXNfdG8gPSAiZHJvcF9tZSIpICU+JSANCiAgZHJvcF9uYSh0YWdzKSAlPiUgDQogIGdyb3VwX2J5KHRhZ3MpICU+JSANCiAgIGNvdW50KHRhZ3MpICU+JSANCiAgIGFycmFuZ2UoZGVzYyhuKSkgDQoNCg0KYGBgDQoNCmBgYHtyfQ0KIyBjcmVhdGluZyBuZXcgbG9naWNhbCBjb2x1bW5zIGZvciB1c2UgaW4gdGhlIG1vZGVsIC0gd2UgY2FudCBoYXZlIGV2ZXJ5IGdlbnJlL3RhZywgc28gYSBzZWxlY3Rpb24gb2YgYSBmZXcgd2lsbCBoYXZlIHRvIGRvDQojIFByZWZlcmFibHkgb25lcyB3aXRob3V0IGEgbG90IG9mIG92ZXJsYXANCiMgTm90IGluY2x1ZGluZyBJbmRpZSBqdXN0IG5vdywgYmVjYXVzZSBhbnl0aGluZyBkZXZlbG9wZWQgYnkgRGlua2V5IGdhbWUgd291bGQgYmUgSW5kaWUgYnkgZGVmYXVsdCAtIEl0J3MgYSB0YWcgdGhhdCBkb2VzbnQgcmVhbGx5IGRlc2NyaWJlIGFueXRoaW5nIGFib3V0IHRoZSBnYW1lDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShpc19vcGVuX3dvcmxkID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIk9wZW4gV29ybGQiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaXNfcm9ndWVfbGlrZSA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJSb2d1ZS1saWtlIiksIFRSVUUsIEZBTFNFKSwNCiAgICAgICAgIGlzX21ldHJvaWR2YW5pYSA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJNZXRyb2lkdmFuaWEiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaXNfdmlzdWFsX25vdmVsID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlZpc3VhbCBOb3ZlbCIpLCBUUlVFLCBGQUxTRSksDQogICAgICAgICBoYXNfZ3JlYXRfc291bmR0cmFjayA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJHcmVhdCBTb3VuZHRyYWNrIiksIFRSVUUsIEZBTFNFKSwNCiAgICAgICAgIGlzX3Nob290ZXIgPWlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJTaG9vdGVyIikgfCBzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJGUFMiKSB8IHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlRoaXJkLVBlcnNvbiBTaG9vdGVyIiksIFRSVUUsIEZBTFNFKSwNCiAgICAgICAgIGlzX3JwZyA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJSUEciKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaGFzX2ZlbWFsZV9wcm90YWdvbmlzdCA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJGZW1hbGUgUHJvdGFnb25pc3QiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaXNfc3BvcnRzID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlNwb3J0cyIpLCBUUlVFLCBGQUxTRSksDQogICAgICAgICBpc19zaW11bGF0aW9uID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlNpbXVsYXRpb24iKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWZ0ZXIgPSBtdWx0aXBsYXllcikgDQoNCmBgYA0KYGBge3J9DQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShpc19wb3NpdGl2ZSA9IGlmZWxzZShzdHJfZGV0ZWN0KGdlbmVyYWxfcmF0aW5nLCAiUG9zaXRpdmUiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgLmFmdGVyID0gbGludXhfc3VwcG9ydCkNCmBgYA0KRm9yIHRoZSBwdXJwb3NlIG9mIG15IG1vZGVsIG92ZXIgaW4gUHl0aG9uIGxhbmQsIGknbSB0dXJuaW5nIE11bHRpcGxheWVyIGludG8ganVzdCBhIGxvZ2ljYWwgdG8gc2VlIGlmIGl0IGNoYW5nZXMgYW55dGhpbmcNCg0KYGBge3J9DQpzdGVhbSAlPiUgDQogIGRpc3RpbmN0KG11bHRpcGxheWVyKQ0KYGBgDQoNCg0KYGBge3J9DQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShtdWx0aXBsYXllciA9IGlmZWxzZShzdHJfZGV0ZWN0KG11bHRpcGxheWVyLCAiTm8gbXVsdGlwbGF5ZXIiKSwgRkFMU0UsIFRSVUUpKSANCmBgYA0KYGBge3J9DQojb2sgZmluZSBpbSBhZGRpbmcgaW5kaWUgdG8gc2VlIHdoYXQgaGFwcGVucyANCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKGlzX2luZGllID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIkluZGllIiksIFRSVUUsIEZBTFNFKSkgDQpgYGANCmBgYHtyfQ0Kc3RlYW0gJT4lIA0KICBmaWx0ZXIobXVsdGlwbGF5ZXIgPT0gRkFMU0UpDQpgYGANCmBgYHtyfQ0KIyB3aGF0IGlmIHdlIGJ1bXBlZCB1cCB0aGUgdGhyZXNob2xkPyBXaGF0IGlmIG1vc3RseSBwb3NpdGl2ZSB3YXMgbm8gbG9uZ2VyIGFjY2VwdGFibGU/DQoNCnN0ZWFtX2J1dF9oYXJzaGVyIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKGlzX3Bvc2l0aXZlID0gY2FzZV93aGVuKA0KICAgIGdlbmVyYWxfcmF0aW5nID09ICJFeHRyZW1lbHkgUG9zaXRpdmUiIHwgZ2VuZXJhbF9yYXRpbmcgPT0gIlBvc2l0aXZlIiB+IFRSVUUsDQogICAgVFJVRSB+IEZBTFNFDQogICkpIA0KYGBgDQoNCmBgYHtyfQ0Kc3RlYW1fYnV0X2hhcnNoZXIgJT4lIA0KICBmaWx0ZXIoaXNfcG9zaXRpdmUgPT0gVFJVRSkNCmBgYA0KYGBge3J9DQpzdGVhbSAlPiUgDQogIGZpbHRlcihpc19wb3NpdGl2ZSA9PSBUUlVFKQ0KYGBgDQoNCg0KYGBge3J9DQp3cml0ZV9jc3Yoc3RlYW0sICJjbGVhbl9kYXRhL3N0ZWFtX2Zvcl9tb2RlbC5jc3YiKQ0Kd3JpdGVfY3N2KHN0ZWFtX2J1dF9oYXJzaGVyLCAiY2xlYW5fZGF0YS9oYXJzaGVyX3N0ZWFtX2Zvcl9tb2RlbC5jc3YiKQ0KYGBgDQoNCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXw0KDQojIEZvciBub3csIGltIGdvbm5hIGRvIHNvbWUgdGV4dCBzdHVmZiB1c2luZyBiYWNrbG9nZ2QgcmV2aWV3cw0KDQpgYGB7cn0NCmJhY2tsb2dnZF9yZXZpZXdzIDwtIHJlYWRfY3N2KCJjbGVhbl9kYXRhL2JhY2tsb2dnZF9yZXZpZXdzLmNzdiIpDQpgYGANCg0KYGBge3J9DQojIHNlcGFyYXRpbmcgcmV2aWV3cyBpbnRvIHRoZWlyIG93biBjb2x1bW5zIHNvIGkgY2FuIG1hbmFnZSB0aGVtDQpyZXZpZXdzX3NlcGFyYXRlZCA8LSBiYWNrbG9nZ2RfcmV2aWV3cyAlPiUgDQogIHNlcGFyYXRlX2xvbmdlcl9kZWxpbShjb2xzID0gInJldmlld3MiLCBkZWxpbSA9ICInLFwgXCciKSAlPiUgDQogIHNlcGFyYXRlX2xvbmdlcl9kZWxpbShjb2xzID0gInJldmlld3MiLCBkZWxpbSA9ICJcJyxcIFwiIikgJT4lIA0KICBzZXBhcmF0ZV9sb25nZXJfZGVsaW0oY29scyA9ICJyZXZpZXdzIiwgZGVsaW0gPSAiXCIsXCBcJyIpICU+JSAgDQogIHNlcGFyYXRlX2xvbmdlcl9kZWxpbShjb2xzID0gInJldmlld3MiLCBkZWxpbSA9ICJcIixcIFwiIikgJT4lIA0KICB1bmlxdWUoKSAlPiUgDQogICMgcmVtb3ZpbmcgYWxsIHRoZSBvcGVuaW5nIGFuZCBjbG9zaW5nIHJ1YmJpc2gNCiAgbXV0YXRlKHJldmlld3MgPSBzdHJfcmVtb3ZlX2FsbChzdHJpbmcgPSByZXZpZXdzLCBwYXR0ZXJuID0gIlxcW1wiIiksDQogICAgICAgICByZXZpZXdzID0gc3RyX3JlbW92ZV9hbGwoc3RyaW5nID0gcmV2aWV3cywgcGF0dGVybiA9ICJcXFtcJyIpLA0KICAgICAgICAgcmV2aWV3cyA9IHN0cl9yZW1vdmVfYWxsKHN0cmluZyA9IHJldmlld3MsIHBhdHRlcm4gPSAiXCJcXF0iKSwNCiAgICAgICAgIHJldmlld3MgPSBzdHJfcmVtb3ZlX2FsbChzdHJpbmcgPSByZXZpZXdzLCBwYXR0ZXJuID0gIlwnXFxdIikpIA0KDQpgYGANCg0KYGBge3J9DQpyZXZpZXdzX3NlcGFyYXRlZCAlPiUgDQogIGdyb3VwX2J5KHRpdGxlKSAlPiUgDQogIGNvdW50KHRpdGxlKQ0KYGBgDQoNCiMgbGV0cyBzcGxpdCB0aGlzIGludG8gcmF0aW5ncywgbGlrZSBpIGRpZCB3aXRoIHRoZSBvdGhlciBiYWNrbG9nZ2QgdGhpbmcNCmBgYHtyfQ0KcmV2aWV3c19zZXBhcmF0ZWQgPC0gcmV2aWV3c19zZXBhcmF0ZWQgJT4lIA0KICBtdXRhdGUocmF0aW5nX3JhbmdlID0gY2FzZV93aGVuKA0KICAgIHJhdGluZyA8IDUgJiByYXRpbmcgPj0gNCB+ICI0KyIsDQogICAgcmF0aW5nIDwgNCAmIHJhdGluZyA+PSAzIH4gIjMgdG8gNCIsDQogICAgcmF0aW5nIDwgMyAmIHJhdGluZyA+PSAyIH4gIjIgdG8gMyIsDQogICAgcmF0aW5nIDwgMiAmIHJhdGluZyA+PSAxIH4gIjEgdG8gMiIsDQogICAgcmF0aW5nIDwgMSB+ICI+MSIsDQogICAgVFJVRSB+ICJObyByYXRpbmciDQogICkpDQpgYGANCg0KQmVmb3JlIGRvaW5nIHRleHQgYW5hbHlzaXMsIGxldHMgY29udmVydCBldmVyeXRoaW5nIHRvIGxvd2VyIGNhc2UgLSB0aGlzIG1pZ2h0IHRha2UgYXdheSBzb21lIGNvbnRleHQgdmlhIGNhcGl0YWxpc2F0aW9uLCBidXQgd2Ugb25seSBoYXZlDQo2IHJldmlld3MgcGVyIGdhbWUgc28gZ290dGEgd29yayB3aXRoIHdoYXQgd2UgaGF2ZQ0KDQpgYGB7cn0NCnJldmlld3NfbG93ZXJfc2VwYXJhdGVkIDwtIHJldmlld3Nfc2VwYXJhdGVkICU+JSANCiAgbXV0YXRlKHJldmlld3MgPSB0b2xvd2VyKHJldmlld3MpKSAjJT4lIA0KICAjZmlsdGVyKHJldmlld3MgJWluJSBjKCLDrSIsICLDsSIsICLDqSIsICLDoSIsICLDsyIsICLDoSIpID09IEZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeSh0ZXh0ZGF0YSkNCmxpYnJhcnkodGlkeXRleHQpDQpgYGANCg0KYGBge3J9DQpkZV9zdG9wd29yZHMgPC0gdGliYmxlKHdvcmQgPSBzdG9wd29yZHMoImRlIikpDQoNCnJldmlld3NfM190b181X3N0b3AgPC0gcmV2aWV3c19sb3dlcl9zZXBhcmF0ZWQgJT4lIA0KICBmaWx0ZXIocmF0aW5nX3JhbmdlID09ICI0KyIgfCByYXRpbmdfcmFuZ2UgPT0gIjMgdG8gNCIpICU+JSANCiAgc2VsZWN0KC1zdW1tYXJ5KSAlPiUgDQogIHVubmVzdF90b2tlbnMoaW5wdXQgPSByZXZpZXdzLCBvdXRwdXQgPSB3b3JkKSAlPiUgDQogIGFudGlfam9pbihmaWx0ZXIoc3RvcF93b3JkcywgbGV4aWNvbiA9PSAiU01BUlQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX3dvcmRzKSAlPiUgDQogIGFudGlfam9pbihwdF9zdG9wd29yZHMpICU+JSANCiAgYW50aV9qb2luKGRlX3N0b3B3b3JkcykgJT4lIA0KICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkNCiAgDQpgYGANCg0KYGBge3J9DQpnZ3dvcmRjbG91ZCh3b3JkcyA9IHJldmlld3NfM190b181X3N0b3Akd29yZCwgZnJlcSA9IHJldmlld3NfM190b181X3N0b3AkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgDQogICAgICAgICAgICBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIiksIG1pbi5mcmVxID0gMTApDQpgYGANCg0KYGBge3J9DQpyZXZpZXdzXzNfdG9fNV9zdG9wDQpgYGANCmBgYHtyfQ0KcmV2aWV3c18zX3RvX2xvd2VzdF9zdG9wIDwtIHJldmlld3NfbG93ZXJfc2VwYXJhdGVkICU+JSANCiAgZmlsdGVyKHJhdGluZ19yYW5nZSA9PSAiMiB0byAzIiB8IHJhdGluZ19yYW5nZSA9PSAiMSB0byAyIiB8IHJhdGluZ19yYW5nZSA9PSAiPjEiKSAlPiUgDQogIHNlbGVjdCgtc3VtbWFyeSkgJT4lIA0KICB1bm5lc3RfdG9rZW5zKGlucHV0ID0gcmV2aWV3cywgb3V0cHV0ID0gd29yZCkgJT4lIA0KICBhbnRpX2pvaW4oZmlsdGVyKHN0b3Bfd29yZHMsIGxleGljb24gPT0gIlNNQVJUIikpICU+JSANCiAgYW50aV9qb2luKGdhbWVfc3RvcF93b3JkcykgJT4lIA0KICBhbnRpX2pvaW4ocHRfc3RvcHdvcmRzKSAlPiUgDQogIGFudGlfam9pbihkZV9zdG9wd29yZHMpICU+JSANCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQoNCmBgYA0KDQpgYGB7cn0NCmdnd29yZGNsb3VkKHdvcmRzID0gcmV2aWV3c18zX3RvX2xvd2VzdF9zdG9wJHdvcmQsIGZyZXEgPSByZXZpZXdzXzNfdG9fbG93ZXN0X3N0b3AkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgDQogICAgICAgICAgICBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIiksIG1pbi5mcmVxID0gNSkNCmBgYA0KYmlncmFtcyBtaWdodCBiZSBtb3JlIGFwcHJvcHJpYXRlIGZvciBiYWQgcmV2aWV3cw0KDQpgYGB7cn0NCmJpZ3JhbXNfM190b19sb3dlc3Rfc3RvcCA8LSByZXZpZXdzX2xvd2VyX3NlcGFyYXRlZCAlPiUgDQogIGZpbHRlcihyYXRpbmdfcmFuZ2UgPT0gIjIgdG8gMyIgfCByYXRpbmdfcmFuZ2UgPT0gIjEgdG8gMiIgfCByYXRpbmdfcmFuZ2UgPT0gIj4xIikgJT4lIA0KICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgcmV2aWV3cywgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpICU+JSANCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVFJVRSkgJT4lICAjIGNvdW50IGJpZ3JhbXMNCiAgc2VwYXJhdGUoYmlncmFtLCBpbnRvID0gYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUgIyBzcGxpdCBiaWdyYW1zIGludG8gdHdvIHNlcGVyYXRlIGNvbHVtbnMNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQxIiA9PSAid29yZCIpKSAlPiUgICMgY2hlY2sgaWYgd29yZDEgb2YgYmlncmFtIGlzIGEgc3RvcCB3b3JkDQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4ocHRfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMSIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4ocHRfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX3dvcmRzLCBqb2luX2J5KCJ3b3JkMSIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX3dvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZGVfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMSIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZGVfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBkcm9wX25hKCkgJT4lIA0KICB1bml0ZShjb2wgPSAiYmlncmFtIiwgd29yZDE6d29yZDIsIHNlcCA9ICIgIiwgcmVtb3ZlID0gVFJVRSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX2JpZ3JhbXMpDQpgYGANCg0KYGBge3J9DQpnZ3dvcmRjbG91ZCh3b3JkcyA9IGJpZ3JhbXNfM190b19sb3dlc3Rfc3RvcCRiaWdyYW0sIGZyZXEgPSBiaWdyYW1zXzNfdG9fbG93ZXN0X3N0b3AkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgDQogICAgICAgICAgICBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIiksIG1pbi5mcmVxID0gMSkNCmBgYA0Kd2VsbCBmaW5lIGxldHMgcmVtb3ZlIGdhbWUgbmFtZXMgYXMgd2VsbA0KDQpvayB0aGF0IHN0aWxsIGtpbmRhIHN1Y2tzDQoNCmBgYHtyfQ0KZ2FtZV9zdG9wX2JpZ3JhbXMgPC0gcmV2aWV3c19iYWNrbG9nZ2QgJT4lIA0KICBzZWxlY3QodGl0bGUpICU+JSANCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRpdGxlLCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lIA0KICBkcm9wX25hKCkNCmBgYA0KDQoNCmBgYHtyfQ0KcmV2aWV3c18zX3RvX2xvd2VzdF9zdG9wDQpgYGANCmBgYHtyfQ0KZ2FtZV9zdG9wX3dvcmRzIDwtIHRpYmJsZSgNCiAgd29yZCA9IGMoImdhbWUiLCAiZ2FtZXBsYXkiLCAidGltZSIsICJwbGF5ZWQiLCAicGxheSIsICJkZSIsICJwbGF5aW5nIiwgImpvZ28iLCAiw6kiLCAiZ2FtZXMiKSAjIGxldHMgdHJ5IGRvaW5nIG4tZ3JhbXMgYmVmb3JlIHBvcHVsYXRpbmcgdGhpcw0KKQ0KYGBgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHN0b3B3b3JkcykNCg0KZXNfc3RvcHdvcmRzIDwtIHRpYmJsZSh3b3JkID0gc3RvcHdvcmRzKCJlcyIpKQ0KcHRfc3RvcHdvcmRzIDwtIHRpYmJsZSh3b3JkID0gc3RvcHdvcmRzKCJwdCIpKQ0KYGBgDQoNCg0KYGBge3J9DQpiaWdyYW1zXzNfdG9fNV9ub19zdG9wIDwtIHJldmlld3NfbG93ZXJfc2VwYXJhdGVkICU+JSANCiAgZmlsdGVyKHJhdGluZ19yYW5nZSA9PSAiNCsiIHwgcmF0aW5nX3JhbmdlID09ICIzIHRvIDQiKSAlPiUgDQogIHVubmVzdF90b2tlbnMoYmlncmFtLCByZXZpZXdzLCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lIA0KICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKSAlPiUgICMgY291bnQgYmlncmFtcw0KICBzZXBhcmF0ZShiaWdyYW0sIGludG8gPSBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpICU+JSAjIHNwbGl0IGJpZ3JhbXMgaW50byB0d28gc2VwZXJhdGUgY29sdW1ucw0KICBhbnRpX2pvaW4oc3RvcF93b3Jkcywgam9pbl9ieSgid29yZDEiID09ICJ3b3JkIikpICU+JSAgIyBjaGVjayBpZiB3b3JkMSBvZiBiaWdyYW0gaXMgYSBzdG9wIHdvcmQNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQyIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihwdF9zdG9wd29yZHMsIGpvaW5fYnkoIndvcmQxIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihwdF9zdG9wd29yZHMsIGpvaW5fYnkoIndvcmQyIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihnYW1lX3N0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQxIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihnYW1lX3N0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQyIiA9PSAid29yZCIpKSAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIHVuaXRlKGNvbCA9ICJiaWdyYW0iLCB3b3JkMTp3b3JkMiwgc2VwID0gIiAiLCByZW1vdmUgPSBUUlVFKQ0KDQpiaWdyYW1zXzNfdG9fNV93X3N0b3AgPC0gcmV2aWV3c19sb3dlcl9zZXBhcmF0ZWQgJT4lIA0KICBmaWx0ZXIocmF0aW5nX3JhbmdlID09ICI0KyIgfCByYXRpbmdfcmFuZ2UgPT0gIjMgdG8gNCIpICU+JSANCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHJldmlld3MsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUgICMgY3JlYXRlIGJpZ3JhbXMNCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVFJVRSkgDQoNCmBgYA0KDQoNCmBgYHtyfQ0KICBnZ3dvcmRjbG91ZCh3b3JkcyA9IGJpZ3JhbXNfM190b181X25vX3N0b3AkYmlncmFtLCBmcmVxID0gYmlncmFtc18zX3RvXzVfbm9fc3RvcCRuLCByYW5kb20uY29sb3IgPSBUUlVFLCBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIikpDQpgYGANCg0Kb2sgd2hhdCBpZiB3ZSB0cnkgc29tZSBvZiB0aGF0IHRmLWlkZiBzdHVmZg0KDQpgYGB7cn0NCnJldmlld3Nfc2VwYXJhdGVkICU+JSANCiAgdW5uZXN0X3Rva2VucyhpbnB1dCA9IHJldmlld3MsIG91dHB1dCA9IHdvcmQpDQpgYGANCmBgDQo=